#include "People.h"
#include "PeopleFunctors.h"
#include "RandomSystem.h"
#include "Utilities.h"
#include "Team.h"
#include "ZStream.h"
#include "MatchData.h"

#include <cmath>

#ifndef DARIO
void Contract::Serialize(ZStream& o_stream) const
{
	o_stream.WriteOpenTag("ContractPeolple");
		o_stream.WriteAttribute("Salary", m_salary);
		o_stream.WriteAttribute("Years", m_years);
	o_stream.WriteCloseTag("ContractPeolple");
}

void Contract::Deserialize(ZStream& i_stream)
{
	i_stream.SkipLine();
		i_stream.ReadAttribute(m_salary);
		i_stream.ReadAttribute(m_years);
	i_stream.SkipLine();
}
#endif

// PeopleParams

PeopleParams People::s_params;

// People

People::People(const STL::string& i_name, const int i_id, const bool i_isPlayer)
{
	Generate(i_name, i_id, i_isPlayer);
}

void People::Generate(const STL::string& i_name, const int i_id, const bool i_isPlayer)
{
	m_availability.Clear();
	m_history.Clear();
	m_skill.Generate(i_isPlayer);

	m_id = i_id;
	m_name = i_name.substr(0, s_params.MAX_CHAR_NAME);
	m_teamID = Utilities::INVALID_ID;

	int age = 0;
	if(i_isPlayer)
	{
		age = 15 + MultiRand(10,2);
	}
	else
	{
		age = 30 + MultiRand(15,2);
		m_history.SetRetirementYear(StatusPlayer);
	}
	m_history.m_birthYear = PeopleHistory::s_currentYear - age;
	m_history.m_startingYear = PeopleHistory::s_currentYear;
	
	m_history.SetStatus(i_isPlayer ? StatusPlayer : StatusTrainer);
	DARIONLY(CalculateSalary();)
}

bool People::SeasonUpdate()
{
	EndSeason();
	return CheckRetirement();
}

void People::EndSeason()
{
	m_skill.m_physicsSkills[Fatigue] = 0;
	m_skill.m_physicsSkills[Stamina] = m_skill.m_physicsSkills[Resistance];
	m_availability.m_daysBroken = 0;
	m_availability.Clear();

#ifndef DARIO
	//update contract
	if(HasContract())
		m_contract.SeasonUpdate();
#else
	CalculateSalary();
#endif
}

int People::GetRetirePercent() const
{
	int percent = 0;
	if(GetStatus() == StatusPlayer)
	{
		const int age = GetHistory().GetAge();
		const int matches = GetHistory().GetMatch();
		const int goals = GetHistory().GetScoredGoal();
		const int moms = GetHistory().GetMoM();
		const int inj = GetHistory().GetTotalNumOfInjury();
		const int bestSkill = m_skill.m_playerSkills.m_skills[m_skill.m_playerSkills.m_bestRole];
		const bool busy = IsBusy();

		percent = (age-30) * ((age>30) ? 5 : 2);
		percent += inj*2;
		percent += (75-bestSkill)/2;
		percent += (busy ? 0 : age);
		percent -= matches/2;
		percent -= goals/2;
		percent -= moms*5;
	}
	else if(GetStatus() == StatusTrainer)
	{
		//TODO: update correctly trainer retirement percent
		percent = 10;

		//const int skill = GetAverageSkill();
		//percent = (m_age-60)*4;				
		//percent += (50-skill)/3;
		//percent += (m_teamID != Utilities::INVALID_ID  ? 0 : m_age/2);
	}

	if(percent <= 0)
		return 0;
	if(percent >= 99)
		return 99;
	return percent;
}

bool People::CheckRetirement()const
{
	const int percent = GetRetirePercent();
	const int diceRoll = Rand()%100;
	if(diceRoll < percent)
		return true; // useful for breakpoints... else it's better "return diceRoll < percent;"
	return false;
}

bool People::BecomeTrainer()
{
	//TODO: better motivation
	if(RandIntBetween(0,5) == 0)
	{
		//TODO: do more stuff...
		DARIONLY(CalculateSalary();)
		return true;
	}
	return false;
}

const unsigned int People::CalculateExpectedSalary() const
{
	unsigned int salary = 1;

	if(GetStatus() == StatusPlayer)
	{
		//TODO: tune it considering (maybe) mental skills and age...
		int expected = static_cast<int>((
			pow(m_skill.m_playerSkills[Attacker]/100.0f , 3.0f) + 
			pow(m_skill.m_playerSkills[Defender]/100.0f , 3.0f) + 
			pow(m_skill.m_playerSkills[Goalkeeper]/100.0f , 3.0f) + 
			pow(m_skill.m_physicsSkills[Resistance]/200.0f , 2.0f) + 
			0.f ) * 50.f);

		salary = static_cast<unsigned int>(STL::max(expected, 1));
	}
	else
	{
		//TODO: trainer salary
	}

	return salary;
}

DARIONLY(void People::CalculateSalary(){m_salary = CalculateExpectedSalary();})

const int People::InjuryTest(const bool i_isTired)
{
	const int age = GetHistory().GetAge();
	int chance = 1;
	chance += GetHistory().GetTotalNumOfInjury()/5;
	chance += m_skill.m_physicsSkills[Fatigue]/5;
	chance += (age>28 ? (age-28)/3 : 0); // 31,32,33 -> +1  34,35,36 -> +2  37,38,39 -> +3 ...
	chance += (i_isTired ? 5 : 0);

	const int diceRoll=Rand()%200;
	if (diceRoll < chance)
	{
		int old_injury = m_availability.m_daysBroken; //injury become worst
		const int difference = chance - diceRoll;
		const int normal = Rand()%(difference+1);
		const int critical = age/3 - Rand()%100;
		const int newInjury = 1 + STL::max(normal, critical);
		m_availability.m_daysBroken = STL::min(newInjury + old_injury, MAX_DAY_OFF);
		return m_availability.m_daysBroken-old_injury;	
	}

	return 0;
}

void People::PlayerPostMatch(const int i_playedRole, const int i_formation, const int i_scoredGoal, 
	const int i_evaluation, const bool i_isMoM, const int i_card, const bool i_isTired, 
	People * const* const i_trainerRoles, int* o_daysPenalty, int* o_daysInjury)
{
	PostMatchPlayerUpdateSkills(i_playedRole, i_formation, i_trainerRoles); // Skills Update
	
	if(o_daysInjury)
		*o_daysInjury = 0;
	if(o_daysPenalty)
		*o_daysPenalty = 0;

	if(i_playedRole != NUM_FORMATION_ROLES)
	{
		const int newInj = InjuryTest(i_isTired); // Injury Check
		if(o_daysInjury)
			*o_daysInjury = newInj;
		
		m_history.AddMatch(i_scoredGoal, i_evaluation, i_isMoM, i_card, newInj); // History Update
		
		if(!IsNoneCard(i_card)) // Penalty Update
		{
			if(IsYellowCard(i_card) && (m_history.GetYellowCard()%4 == 0))
				m_availability.IncDaysExpulsion(m_history.GetYellowCard()/4);
			if(IsRedCard(i_card))
				m_availability.IncDaysExpulsion(Rand()%i_card + 1);
		}
		if(o_daysPenalty)
			*o_daysPenalty = m_availability.GetDaysExpulsion();
	}
	else
	{
		m_availability.InjuryHeal(); // Injury and Fatigue
		GetAvailability().DecDaysExpulsion(); // Penalty
	}

	PostMatchPlayerUpdateStamina();
}

void People::PostMatchPlayerUpdateSkills(const int i_playedRole, const int i_formation, People * const* const i_trainerRoles)
{
	for(int i=0; i< NUM_SKILL_ROLES; ++i)
	{
		//TODO: choose carefully which trainer trains what
		int t_skill = i_trainerRoles[i] ? i_trainerRoles[i]->m_skill.m_trainerSkills[i] : 0;
		const int perc = Team::ROLE_ROLE_PERCENT_PLAYING[i_formation][i_playedRole][i];
		const bool play = i_playedRole != NUM_FORMATION_ROLES;

		PostMatchPlayerUpdatePlayerSkill(m_skill.m_playerSkills[i], t_skill, perc, play);
		PostMatchPlayerUpdateTrainerSkill(m_skill.m_playerSkills[i], perc, play);
		
		if(m_skill.m_playerSkills[i] > m_skill.m_maxPlayerSkills[i])
			m_skill.m_maxPlayerSkills[i] = m_skill.m_playerSkills[i];
	}
	PostMatchPlayerUpdatePhysicsSkills(i_playedRole);
	
	m_skill.m_playerSkills.CalculateBestRole();
	m_skill.m_trainerSkills.CalculateBestRole();
}

void People::PostMatchPlayerUpdatePlayerSkill(int& o_skill, const int /*i_tSkill*/, const int i_playedRolePercent, const bool i_play)
{
	int diceRoll = RandIntBetween(-20,120);

	diceRoll += 25-GetHistory().GetAge();
	diceRoll += (i_play ? i_playedRolePercent/10  : -5);
		
	//TODO: at the moment trainer influence is disabled
	//diceRoll += (i_tSkill/10 - 5); // trainer value into [-5,5]

	TryUpSkill(o_skill, diceRoll);
}

void People::PostMatchPlayerUpdatePhysicsSkills(const int i_playedRole)
{
	const int up = i_playedRole != NUM_FORMATION_ROLES;
	// resistance
	int diceRoll = RandIntBetween(-10,110);
	int ageDiff = 25-GetHistory().GetAge();
	int ageFactor = ageDiff;
	do
	{
		ageDiff += 5;
		if(ageDiff<0)
			ageFactor += ageDiff;
	}while(ageDiff<0);

	diceRoll += ageFactor;
	diceRoll += up ? 5 : -5;
	TryUpSkill(m_skill.m_physicsSkills[Resistance], diceRoll);

	// fatiguee
	m_skill.m_physicsSkills[Fatigue] = STD::max(m_skill.m_physicsSkills[Fatigue] + s_params.FATIGUE_UPDATE[i_playedRole], 0);

	// stamina update moved after injury check
}

void People::PostMatchPlayerUpdateStamina()
{
	m_skill.m_physicsSkills[Stamina] = 
		(m_availability.m_daysBroken != 0) ? 0 :
		m_skill.m_physicsSkills[Resistance] - static_cast<int>(pow(static_cast<float>(m_skill.m_physicsSkills[Fatigue]), 1.25f));
}

void People::PostMatchPlayerUpdateTrainerSkill(int& /*o_skill*/, const int /*i_playedRolePercent*/, const bool /*i_play*/)
{
	//TODO
}

void People::TryUpSkill(int& o_skill, const int i_diceRoll)
{
	if(i_diceRoll <= 0)
	{
		const int downShot = RandIntBetween(1, 50);
		if(downShot < o_skill)
			--o_skill;
	}
	else if(i_diceRoll >= 100)
	{
		const int upShot = RandIntBetween(101, s_params.max_skill_value);
		if(upShot > o_skill)
			++o_skill;
	}
}

void People::TrainerPostMatch(const int i_trainedRoles)
{
	// skills update
	PostMatchTrainerUpdateSkills(i_trainedRoles);

	// History Update
	if(i_trainedRoles != TrNone)
		m_history.AddMatch(0,0,false,0,0); 
}

void People::PostMatchTrainerUpdateSkills(const int /*i_trainedRoles*/)
{
	//TODO
}

ZStream& People::Serialize(ZStream& o_stream) const
{
	o_stream.WriteOpenTag("People");
	o_stream.WriteAttribute("Name",					m_name);
	o_stream.WriteAttribute("ID",					m_id);
	o_stream.WriteAttribute("TeamID",				m_teamID);
	DARIONLY(o_stream.WriteAttribute("Salary",				m_salary);)
	m_availability.Serialize(o_stream);
	m_skill.Serialize(o_stream);				
	m_history.Serialize(o_stream);
	NOTDARIONLY(m_contract.Serialize(o_stream);)
	o_stream.WriteCloseTag("People");

	return o_stream;
}

ZStream& People::Deserialize(ZStream& i_stream)
{
	i_stream.SkipLine();
	i_stream.ReadAttribute(m_name);
	i_stream.ReadAttribute(m_id);
	i_stream.ReadAttribute(m_teamID);
	DARIONLY(i_stream.ReadAttribute(m_salary);)
	m_availability.Deserialize(i_stream);
	m_skill.Deserialize(i_stream);
	m_history.Deserialize(i_stream);
	NOTDARIONLY(m_contract.Deserialize(i_stream);)
	i_stream.SkipLine();

	return i_stream;
}

